﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Core.Primitives;

namespace Core
{
    public static class Smoother
    {
        #region PUBLIC ACCESS
        public static SinglePolyline Smooth(DataField field, SinglePolyline contour)
        {
            const int pointsPerBezier = 10;
            const float smoothFactor = 1f;

            //contour = Smoother.CutAngles(field, contour);

            return Smoother.BezierSmooth(field, contour, pointsPerBezier, smoothFactor);
        }
        #endregion

        #region PRIVATE ACCESS
        private static SinglePolyline BezierSmooth(DataField field, SinglePolyline contour, int pointPerBezier, float K = 1f)
        {
            // http://www.antigrain.com/research/bezier_interpolation/index.html
            //
            // step 1. Calculate A-points (look site link)

            int k = contour.Length;

            CircularArray<SinglePoint> A = new CircularArray<SinglePoint>(k);
            for (int i = 0; i < k; i++)
            {
                A[i] = 0.5f * (contour.Points[i - 1] + contour.Points[i]);
            }

            // step 2. Calculate B-points
            CircularArray<SinglePoint> B = new CircularArray<SinglePoint>(k);
            for (int i = 0; i < k; i++)
            {
                // segment (A[i - 1]; A[i])
                // A[i] point lying on (points[i]; points[i+1]) segment
                SinglePoint pA0 = A[i];
                SinglePoint pA1 = A[i + 1];
                SingleSegment ASegment = new SingleSegment(pA0, pA1);
                SingleSegment L1Segment = contour.GetSegment(i);
                SingleSegment L2Segment = contour.GetSegment(i + 1);

                float ratio = L1Segment.Length() / L2Segment.Length();
                float d1 = ASegment.Length() / (1.0f + 1.0f / ratio);

                float t = d1 / ASegment.Length();

                B[i] = pA0 * (1 - t) + pA1 * t;
            }

            // step 3. Move calculated line segments

            CircularArray<SinglePoint> anchors = new CircularArray<SinglePoint>(2 * k);
            for (int i = 0; i < k; i++)
            {
                SinglePoint knot = contour.Points[i];

                if (knot.X == field.BoundingRectangle.Left || knot.Y == field.BoundingRectangle.Bottom
                    || knot.X == field.BoundingRectangle.Right || knot.Y == field.BoundingRectangle.Top)
                {
                    // disable smooth bounding edge
                    anchors[2 * i] = knot;
                    anchors[2 * i + 1] = knot;
                }
                else
                {
                    SinglePoint bPoint = B[i];
                    SinglePoint segmentShift = knot - bPoint;

                    SinglePoint anchor1 = A[i] + segmentShift;
                    SinglePoint anchor2 = A[i + 1] + segmentShift;

                    anchors[2 * i] = anchor1;
                    anchors[2 * i + 1] = anchor2;
                }
            }

            // step 4. Scale anchors
            for (int i = 0; i < k; i++)
            {
                SinglePoint knot = contour.Points[i];

                anchors[2 * i] = knot + (anchors[2 * i] - knot) * K;
                anchors[2 * i + 1] = knot + (anchors[2 * i + 1] - knot) * K;
            }

            CircularArray<SinglePoint> newPolylinePoints = new CircularArray<SinglePoint>(3 * k);

            for (int i = 0; i < k; i++)
            {
                newPolylinePoints[3 * i] = contour.Points[i];
                newPolylinePoints[3 * i + 1] = anchors[2 * i + 1];
                newPolylinePoints[3 * i + 2] = anchors[2 * i + 2];
            }

            return SmoothCubic(new SinglePolyline(newPolylinePoints.Array, contour.Type), pointPerBezier);
        }
        //private static bool IsMainGridKnotPoint(DataField field, SinglePoint p)
        //{
        //    float j = (p.X - field.StartX) / field.StepX;
        //    float i = (p.Y - field.StartY) / field.StepY;

        //    if (j - Math.Floor(j) < float.Epsilon && i - Math.Floor(i) < float.Epsilon)
        //        return true;

        //    return false;
        //}
        //private static bool IsCrossOrBoundPoint(DataField field, SinglePoint p)
        //{
        //    // find discrete position
        //    int j = (int)((p.X + (field.StepX / 2) - field.StartX) / field.StepX);
        //    int i = (int)((p.Y + (field.StepY / 2) - field.StartY) / field.StepY);

        //    if (i == 0 || j == 0 || i == field.Size.Height || j == field.Size.Width) // bound
        //        return true;

        //    // check by cross point
        //    //if (field[i, j].Type == field[i - 1, j - 1].Type
        //    //    && field[i - 1, j].Type == field[i, j - 1].Type
        //    //    && field[i - 1, j].Type != field[i, j].Type)
        //    //    return true;

        //    return false;
        //}
        //private static SinglePolyline CutAngles(DataField field, SinglePolyline contour)
        //{
        //    contour = contour.AddIntermediatePoints(1);

        //    int k = contour.Length;

        //    LinkedList<SinglePoint> newPoints = new LinkedList<SinglePoint>();

        //    for (int i = 1; i <= k; i += 2)
        //    {
        //        SinglePoint p0 = contour.Points[i];
        //        SinglePoint p1 = contour.Points[i + 1];
        //        SinglePoint p2 = contour.Points[i + 2];

        //        newPoints.AddLast(p0);

        //        if (IsCrossOrBoundPoint(field, p1))
        //            newPoints.AddLast(p1);
        //    }

        //    return new SinglePolyline(newPoints.ToArray(), contour.Type);
        //}
        private static SinglePolyline SmoothQuadratic(SinglePolyline contour, int pointPerBezier)
        {
            int k = contour.Length;
            LinkedList<SinglePoint> newPoints = new LinkedList<SinglePoint>();

            for (int i = 0; i < k; i += 2)
            {
                SinglePoint p0 = contour.Points[i];
                SinglePoint p1 = contour.Points[i + 1];
                SinglePoint p2 = contour.Points[i + 2];

                for (int added = 0; added < pointPerBezier; added++)
                {
                    float t = (float)added / (pointPerBezier - 1);

                    newPoints.AddLast(CalculateQuadraticBezierCurvePoint(p0, p1, p2, t));
                }
            }

            return new SinglePolyline(newPoints.ToArray(), contour.Type);
        }
        private static SinglePolyline SmoothCubic(SinglePolyline contour, int pointPerBezier)
        {
            int k = contour.Length;
            LinkedList<SinglePoint> newPoints = new LinkedList<SinglePoint>();

            for (int i = 0; i < k; i += 3)
            {
                SinglePoint p0 = contour.Points[i];
                SinglePoint p1 = contour.Points[i + 1];
                SinglePoint p2 = contour.Points[i + 2];
                SinglePoint p3 = contour.Points[i + 3];

                for (int added = 0; added < pointPerBezier; added++)
                {
                    float t = (float)added / (pointPerBezier - 1);

                    newPoints.AddLast(CalculateCubicBezierCurvePoint(p0, p1, p2, p3, t));
                }
            }

            return new SinglePolyline(newPoints.ToArray(), contour.Type);
        }
        private static SinglePoint CalculateQuadraticBezierCurvePoint(SinglePoint p0, SinglePoint p1, SinglePoint p2, float t)
        {
            /// <see cref="http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves"/>
            return (1 - t) * (1 - t) * p0 + 2 * t * (1 - t) * p1 + t * t * p2;
        }
        private static SinglePoint CalculateCubicBezierCurvePoint(SinglePoint p0, SinglePoint p1, SinglePoint p2, SinglePoint p3, float t)
        {
            /// <see cref="http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves"/>
            return (1 - t) * (1 - t) * (1 - t) * p0 + 3 * t * (1 - t) * (1 - t) * p1 + 3 * (1 - t) * t * t * p2 + t * t * t * p3;
        } 
        #endregion
    }
}
